/*
 *
 * @ProjectName:    my_umi_app
 * @Author:         SuPeiSen
 * @CreateDate:     2020/12/21 4:38 下午
 * @UpdateDate:     2020/12/21 4:38 下午
 * @UpdateRemark:   更新说明：Axios请求封装
 * @Version:        1.0
 * @参考：           https://cloud.tencent.com/developer/article/1611492
 */
import axios, {
  AxiosRequestConfig,
  AxiosResponse,
  CancelTokenSource,
} from 'axios';
import { Local, LocalKey } from '@/utils/cache';
import { ApiPath } from '@/config/api_config';
import { history } from 'umi';

// 用于表示本次请求的状态(例如等待中，请求成功，请求失败)
export enum HttpStatus {
  loading,
  success,
  error,
}

export enum RequestsStatus {
  init,
  success,
  unSuccess,
  error,
  finally,
}

enum RequestsListCallBackEnum {
  initIndex,
  successIndex,
  unSuccessIndex,
  errorIndex,
  finallyIndex,
}

// 定义一些callBack
type SuccessCallBack<Data> = (
  response: AxiosResponse<AxiosResult<Data>>,
) => void;
type UnSuccessCallBack<Data> = (
  response: AxiosResponse<AxiosResult<Data>>,
) => void;
type ErrorCallBack<Data> = (
  // response: AxiosResponse<AxiosResult<Data>>,
  e: any,
) => void;
type FinallyCallBack = () => void;
type InitCallBack = () => void;

// 一个请求相关回调
type CallBackList<Data> = [
  InitCallBack, // 初始化回调
  SuccessCallBack<Data>, // 请求成功回调
  UnSuccessCallBack<Data>, // 请求的success_code !== 200 时候回调
  ErrorCallBack<Data>, // 出现异常回调
  FinallyCallBack, // 执行完请求回调
];

// 约定一个回调，用于规定请求过程的callback
interface AxiosCallBack<Data = any> {
  success?: SuccessCallBack<Data>;
  unSuccess?: UnSuccessCallBack<Data>;
  error?: ErrorCallBack<Data>;
  finally?: FinallyCallBack;
  init?: InitCallBack;
  // 如果上面有一个callback是不为undefined, 则
  // 则callbackList则为undefined
  // ps: 这只是一个语法糖，不用每次都写五个回调，直接把五个回调写到
  // 一个数组里面，更加方便。
  callbackList?: CallBackList<Data>;
}

// 约定一个请求结果，用于规定接口正确返回的数据
interface AxiosResult<Data> {
  code: number;
  msg: string;
  data: Data;
}

// 统一返回请求对象
export interface ResponseModel<Data> {
  data?: AxiosResult<Data>;
  success: boolean;
  error: boolean;
  code?: number;
  msg?: string;
  response?: AxiosResponse<Data>;
}

// 约定一个key=>value对象，用于规定请求数据
export interface AxiosParam {
  [key: string]: string | number;
}

// 约定一个config参数，用于规定传入请求数据
interface AxiosConfig<Data> {
  getData?: AxiosParam;
  postData?: any;
  header?: AxiosParam;
  callBack?: AxiosCallBack<Data>;
  extra?: AxiosExtra;
}

interface AxiosExtra {
  onUploadCallBack(progressEvent: any): void;
}

// 约定formData
interface FormDataConfig<Data> {
  postData?: any;
  header?: AxiosParam;
  callBack?: AxiosCallBack<Data>;
}

// 约定RefreshToken的类型
type RefreshTokenType = { token: string };

// 约定请求类型
type AxiosMethod = 'get' | 'post' | 'delete' | 'put' | 'patch';

// 请求的一些相关配置
const RequestConfig = {
  baseUrl: process.env.REQUEST_BASE_URL, // 主域名
  successCode: 200, // 代表本次请求成功
  backTokenCode: 40003, // 代表token失效，需要拿RefreshToken换取新的token
  log: process.env.REQUEST_LOG, // 是否打印每次请求日志
};

/**
 * 封装Axios请求
 */
export class Requests<Data = any> {
  // 一个axios实例
  private instance = axios.create({
    baseURL: RequestConfig.baseUrl,
    timeout: 50000,
  });

  // 加锁🔒
  private lock = false;
  // token是否失效
  private tokenFailure = false;
  // 挂起请求队列
  private httpQueue: Array<{ resolved: (value: any) => void }> = [];

  // 这个东西是一次性的，用完重新获取
  private static source: CancelTokenSource;

  // 获取cancelToken，用于取消请求
  public static get cancelToken() {
    if (this.source === undefined) {
      this.source = axios.CancelToken.source();
    }
    return this.source;
  }

  // 暴露方便获取Request对象
  public static readonly defaults = Requests.getDefaults();
  private static defaultsObj: Requests;

  private static getDefaults(): Requests {
    if (this.defaultsObj === undefined) {
      this.defaultsObj = new Requests();
    }
    return this.defaultsObj;
  }

  /**
   * http请求
   * @param method 请求方式
   * @param url 请求地址
   * @param data 一些额外的参数
   * @param authorization 权限
   */
  public async http<Data>(
    method: AxiosMethod,
    url: string,
    data?: AxiosConfig<Data>,
    authorization?: string | boolean,
  ): Promise<ResponseModel<Data>> {
    // 添加拦截器，统一加上token
    this.addInterceptor<Data>(authorization);
    return this.toHttp(method, url, data);
  }

  /**
   * 发送http不需要携带token
   * @param method
   * @param url
   * @param data
   */
  public async httpNo<Data>(
    method: AxiosMethod,
    url: string,
    data?: AxiosConfig<Data>,
  ): Promise<ResponseModel<Data>> {
    // // 添加拦截器，统一加上token
    // this.addInterceptor<Data>(false);
    return this.toHttp(method, url, data);
  }

  /**
   * 发起formData
   * @param url
   * @param data
   * @param authorization
   */
  public async formData<Data>(
    url: string,
    data?: AxiosConfig<Data>,
    authorization?: string | boolean,
  ): Promise<ResponseModel<Data>> {
    // 添加拦截器，统一加上token
    this.addInterceptor<Data>(authorization);
    return this.toHttp('post', url, data);
  }

  // 发起请求
  private async toHttp<Data>(
    method: AxiosMethod,
    url: string,
    data?: AxiosConfig<Data>,
  ) {
    data = this.checkCallBackList(data);
    this.requestCallBack<Data>(RequestsListCallBackEnum.initIndex, data);
    this.printLog('发起' + method + '网络请求', { data: data, url: url });
    try {
      let response: AxiosResponse<AxiosResult<Data>> | undefined;
      switch (method) {
        case 'get':
          response = await this.getMethod<Data>(url, data);
          break;
        case 'post':
          response = await this.postMethod<Data>(url, data);
          break;
        case 'delete':
          response = await this.deleteMethod<Data>(url, data);
          break;
        case 'patch':
          break;
        case 'put':
          response = await this.putMethod<Data>(url, data);
          break;
      }
      const noErrorModel = await this.checkResponse<Data>(response!, data);
      this.requestCallBack<Data>(RequestsListCallBackEnum.finallyIndex, data);
      return noErrorModel;
    } catch (e) {
      const errorModel = {
        success: false,
        data: undefined,
        error: true,
        code: undefined,
        msg: String(e),
      };
      if (data?.callBack?.error != undefined) {
        data.callBack.error(e);
      } else if (data?.callBack?.callbackList !== undefined) {
        data?.callBack?.callbackList?.[RequestsListCallBackEnum.errorIndex](e);
      }
      if (data?.callBack?.finally != undefined) {
        data.callBack.finally();
      } else if (data?.callBack?.callbackList !== undefined) {
        data?.callBack?.callbackList?.[RequestsListCallBackEnum.finallyIndex]();
      }
      this.printLog('网络请异常', { isError: true, url: url });
      return errorModel;
    }
  }

  // 添加拦截器
  private addInterceptor<Data>(authorization?: string | boolean) {
    // 请求拦截, 在请求添加token
    if (authorization === undefined) {
      this.instance.interceptors.request.use((config) => {
        config.headers['Authorization'] = 'Bearer ' + Local.token;
        return config;
      });
    } else if (typeof authorization === 'string') {
      this.instance.interceptors.request.use((config) => {
        config.headers['Authorization'] = authorization;
        return config;
      });
    }
  }

  // 如有一个callback不为undefined，则callbackList必须为undefined
  private checkCallBackList<Data>(
    data?: AxiosConfig<Data>,
  ): AxiosConfig<Data> | undefined {
    if (data?.callBack !== undefined) {
      const callBack = data.callBack;
      if (
        callBack.init !== undefined ||
        callBack.success !== undefined ||
        callBack.unSuccess !== undefined ||
        callBack.error !== undefined ||
        (callBack.finally !== undefined && callBack.callbackList !== undefined)
      ) {
        data.callBack.callbackList = undefined;
        return data;
      }
      return data;
    }
    return data;
  }

  // 对不同code值进行操作
  private async checkResponse<Data>(
    response: AxiosResponse<AxiosResult<Data>>,
    data?: AxiosConfig<Data>,
  ): Promise<ResponseModel<Data>> {
    const rspCode = response.data.code;
    // 表示请求成功
    if (rspCode === RequestConfig.successCode) {
      this.requestCallBack<Data>(
        RequestsListCallBackEnum.successIndex,
        data,
        response,
      );
      this.printLog('网络请求成功', {
        url: response.config.url,
        config: response.config,
        data: response.data,
      });
      return {
        success: true,
        data: response.data,
        error: false,
        code: response.data.code,
        msg: response.data.msg,
      };
    }

    // 处理token失效，用RefreshToken重新获取Token
    if (rspCode === RequestConfig.backTokenCode) {
      this.printLog('Token失效', {
        isError: true,
        config: response.config,
        data: response.data,
        url: response.config.url,
      });
      const rsp = await this.getTokenByRefreshToken<Data>(response);
      this.requestCallBack<Data>(
        RequestsListCallBackEnum.successIndex,
        data,
        rsp,
      );
      this.printLog('挂起的请求重新执行成功', {
        data: rsp.data,
        config: rsp.config,
      });
      return {
        success: true,
        data: rsp.data,
        error: false,
        code: rsp.data.code,
        msg: rsp.data.msg,
      };
    }
    this.printLog('状态码非200', {
      data: response.data,
      config: response.config,
    });
    this.requestCallBack<Data>(
      RequestsListCallBackEnum.unSuccessIndex,
      data,
      response,
    );
    return {
      success: false,
      data: response.data,
      error: false,
      code: response.data.code,
      msg: response.data.msg,
    };
  }

  // 获取Account_Token
  private async getAccount() {
    const token = await this.buildToken();
    if (token === undefined) {
      // 这几步是关键 挂起请求
      const externalControl: { resolved: (value: any) => void } = {
        resolved: (value: any) => {},
      };
      const interceptPromise = new Promise((resolved) => {
        externalControl.resolved = resolved;
      });
      this.httpQueue.push(externalControl);
      return interceptPromise;
    }
    console.log('本地获取Token');
    return token!;
  }

  // 生成Account_Token
  private async buildToken() {
    console.log({ lock: this.lock, tokenFailure: this.tokenFailure });
    // 避免重复请求
    if (this.lock) return;
    if (this.tokenFailure) {
      // 第一次发现Token失效的时候，先加锁，挂起后续的请求
      this.lock = true;
      return await this.refreshToken();
    }
    return Local.token;
  }

  // 通过Refresh_Token
  private async refreshToken(): Promise<string> {
    this.printLog('触发RefreshToken机制', {
      isError: true,
      url: ApiPath.getTokenByRefreshToken,
    });
    const model = await Requests.defaults.http<RefreshTokenType>(
      'get',
      ApiPath.getTokenByRefreshToken,
      {
        header: {
          'refresh-token': Local.refreshToken,
        },
      },
    );
    if (model.code !== RequestConfig.successCode) {
      history.push('/login');
      throw new Error();
    }
    const token = model!.data!.data.token;
    // 保存Token到本地存储
    Local.setStr(LocalKey.appToken, token);
    // 设置Token的状态为有效的（取消🔒，执行队列中之前挂起的网络请求）
    this.tokenFailure = false;
    this.lock = false;
    this.httpQueue.forEach((item) => {
      item.resolved(token);
    });
    // 清空队列
    this.httpQueue = [];
    return token;
  }

  // 重新获取token
  private async getTokenByRefreshToken<Data>(
    response: AxiosResponse<AxiosResult<Data>>,
  ): Promise<AxiosResponse<AxiosResult<Data>>> {
    // 设置Token的状态为有效的
    this.tokenFailure = true;
    // // 如果能够拿到Token，则说明能刷新成功，进行下一步重新请求
    const newToken = await this.getAccount();
    response.config.headers['Authorization'] = 'Bearer ' + newToken;
    return await axios.request<AxiosResult<Data>>(response.config);
  }

  // 发起get请求
  private async getMethod<Data>(
    url: string,
    data?: AxiosConfig<Data>,
  ): Promise<AxiosResponse<AxiosResult<Data>>> {
    const config = this.makeConfig(data);
    return await this.instance.get<AxiosResult<Data>>(url, config);
  }

  // 发起post请求
  private async postMethod<Data>(
    url: string,
    data?: AxiosConfig<Data>,
  ): Promise<AxiosResponse<AxiosResult<Data>>> {
    const config = this.makeConfig(data);
    return await this.instance.post<AxiosResult<Data>>(
      url,
      data?.postData,
      config,
    );
  }

  // 发起put请求
  private async putMethod<Data>(
    url: string,
    data?: AxiosConfig<Data>,
  ): Promise<AxiosResponse<AxiosResult<Data>>> {
    const config = this.makeConfig(data);
    return await this.instance.put<AxiosResult<Data>>(
      url,
      data?.postData,
      config,
    );
  }

  // 发起delete请求
  private async deleteMethod<Data>(
    url: string,
    data?: AxiosConfig<Data>,
  ): Promise<AxiosResponse<AxiosResult<Data>>> {
    const config = this.makeConfig<Data>(data);
    return await this.instance.delete<AxiosResult<Data>>(url, config);
  }

  // 获取把参数data转成AxiosRequestConfig
  private makeConfig<Data>(data?: AxiosConfig<Data>): AxiosRequestConfig {
    return {
      params: data?.getData,
      data: data?.postData,
      headers: data?.header,
      onUploadProgress: data?.extra?.onUploadCallBack,
    };
  }

  // 打印网络请求
  private printLog(
    msg: string,
    add: { isError?: boolean; data?: any; config?: any; url?: string },
  ) {
    if (RequestConfig.log) {
      if (!add.isError || undefined) {
        console.log({
          log: 'Request-Log',
          msg: msg,
          data: add.data ?? '',
          config: add.config ?? '',
          url: add.url ?? '',
          time: new Date(),
        });
      } else {
        console.error({
          log: 'Request-UnSuccess',
          msg: msg,
          data: add.data ?? '',
          config: add.config ?? '',
          url: add.url ?? '',
          time: new Date(),
        });
      }
    }
  }

  /**
   * 触发回调
   * @param type
   * @param data
   * @param rsp
   * @param e
   * @private
   */
  private requestCallBack<Data>(
    type: RequestsListCallBackEnum,
    data?: AxiosConfig<Data>,
    rsp?: AxiosResponse<AxiosResult<Data>>,
    e?: any,
  ) {
    switch (type) {
      case RequestsListCallBackEnum.initIndex:
        if (data?.callBack?.init !== undefined) {
          data.callBack.init();
        } else if (data?.callBack?.callbackList !== undefined) {
          data?.callBack?.callbackList?.[RequestsListCallBackEnum.initIndex]();
        }
        break;
      case RequestsListCallBackEnum.successIndex:
        if (data?.callBack?.success !== undefined) {
          data.callBack.success(rsp!);
        } else if (data?.callBack?.callbackList !== undefined) {
          data?.callBack.callbackList?.[RequestsListCallBackEnum.successIndex](
            rsp!,
          );
        }
        break;
      case RequestsListCallBackEnum.unSuccessIndex:
        if (data?.callBack?.unSuccess !== undefined) {
          data.callBack.unSuccess(rsp!);
        } else if (data?.callBack?.callbackList !== undefined) {
          data?.callBack.callbackList?.[
            RequestsListCallBackEnum.unSuccessIndex
          ](rsp!);
        }
        break;
      case RequestsListCallBackEnum.errorIndex:
        if (data?.callBack?.error !== undefined) {
          data.callBack.error(rsp!);
        } else if (data?.callBack?.callbackList !== undefined) {
          data?.callBack.callbackList?.[RequestsListCallBackEnum.errorIndex](
            e!,
          );
        }
        break;
      case RequestsListCallBackEnum.finallyIndex:
        if (data?.callBack?.finally != undefined) {
          data.callBack.finally();
        } else if (data?.callBack?.callbackList !== undefined) {
          data?.callBack.callbackList?.[
            RequestsListCallBackEnum.finallyIndex
          ]();
        }
        break;
    }
  }
}
